package com.tinem.platform.web.gateway.predicate.read;

import com.tinem.platform.module.pojo.co.GatewayHeadName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.function.Predicate;

/**
 * @author fzh
 * @version v1
 * @program: platform
 * @className PlatformReadBodyPredicateFactory
 * @description
 * @site
 * @company
 * @create 2021-06-16 10:18
 */
@Component("PlatformReadReqBodyPredicateFactory")
public class PlatformReadReqBodyPredicateFactory extends ReadBodyPredicateFactory {

    protected static final Log log = LogFactory.getLog(ReadBodyPredicateFactory.class);

    private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";

    private static final List<HttpMessageReader<?>> MESSAGE_READER_LIST = HandlerStrategies
            .withDefaults().messageReaders();

    public PlatformReadReqBodyPredicateFactory() {
    }

    @Override
    @SuppressWarnings("unchecked")
    public AsyncPredicate<ServerWebExchange> applyAsync(ReadBodyPredicateFactory.Config config) {
        return new AsyncPredicate<ServerWebExchange>() {
            @Override
            public Publisher<Boolean> apply(ServerWebExchange exchange) {
                Class inClass = java.lang.String.class;

                Object cachedBody = exchange.getAttribute(GatewayHeadName.X_PLATFORM_GATEWAY_REQ_BODY);
                Mono<?> modifiedBody;
                // We can only read the body from the request once, once that happens if
                // we try to read the body again an exception will be thrown. The below
                // if/else caches the body object as a request attribute in the
                // ServerWebExchange so if this filter is run more than once (due to more
                // than one route using it) we do not try to read the request body
                // multiple times
                if (cachedBody != null) {
                    try {
                        boolean test = true;
                        exchange.getAttributes().put(TEST_ATTRIBUTE, test);
                        return Mono.just(test);
                    }
                    catch (ClassCastException e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Predicate test failed because class in predicate "
                                    + "does not match the cached body object", e);
                        }
                    }
                    return Mono.just(false);
                }
                else {
                    return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
                            (serverHttpRequest) -> ServerRequest
                                    .create(exchange.mutate().request(serverHttpRequest)
                                            .build(), MESSAGE_READER_LIST)
                                    .bodyToMono(inClass)
                                    .doOnNext(objectValue -> exchange.getAttributes().put(
                                            GatewayHeadName.X_PLATFORM_GATEWAY_REQ_BODY, objectValue))
                                    .map(objectValue -> true));
                }
            }

            @Override
            public String toString() {
                return String.format("ReadBody: %s", java.lang.String.class);
            }
        };
    }

    @Override
    @SuppressWarnings("unchecked")
    public Predicate<ServerWebExchange> apply(ReadBodyPredicateFactory.Config config) {
        throw new UnsupportedOperationException(
                "ReadBodyPredicateFactory is only async.");
    }
}
